Nalezení a vytvoření odkazu se může na první pohled zdát jako jednoduchá záležitost. První problém nastane hned při zamyšlení nad formátem URL. Druhý problém je hledání URL v HTML. Zde totiž může být nalezená URL hodnota atributu, nebo potomek již nějakého tagu <a>, a tyto URL se určitě obalit dalším tagem <a> nesmí.
<!-- Zde URL nahradit chceme --> Lorem ipsum http://www.google.com dolor sit amet <!-- Zde určitě ne, je v atributu --> <img src="https://www.kutac.cz/favicon.ico"> <a href="http://www.google.com"> <span> <!-- Zde také ne, nějaký předek již je odkaz --> Go to http://www.google.com </span> </a>
Otestovat JavaScriptovou verzi je možné na stránce testdata.kutac.cz
Regulární výraz pro nalezení URL
Formát URL je komplikovaný a napsat pro něj regulární výraz není jednoduché. Mnou použitý rozhodně není bulletproof řešení, ale měl by na většinu dostačovat. Určitě nepodporuje HTTP Basic auth přímo v URL adrese a jiné protokoly než HTTP a HTTPS, které musejí být přítomny.
V případě potřeby lze regulární výraz nahradit. Například z regexr.com.
var urlReg = /((?:https?|ftps?):\/\/|www.)([\w-]+(?:(?:\.[\w-]+)+))(:[0-9]+)?(\/(?:[\w-\.%]+\/?)*\/?)?(\?[\w-%\.]+[^#\s]+)?(#[^\s]+)?/ig;
PHP a plain text
Pokud je výstup čistý text bez HTML, stačí každou nalezenou URL obalit do tagu <a> a je vyhráno. Zde nehrozí žádná kolize a pro nahrazení lze využít funkce preg_replace.
$text = preg_replace("/((?:https?|ftps?):\/\/|www.)([\w-]+(?:(?:\.[\w-]+)+))(:[0-9]+)?(\/(?:[\w-\.%]+\/?)*\/?)?(\?[\w-%\.]+[^#\s]+)?(#[^\s]+)?/i", '<a href="$0">$0</a>', $text);
PHP a HTML text
Pokud je nutné provést nahrazení v HTML, ale nelze použít JavaScript níže, je nutné podobnou logiku převést do PHP. Pro procházení DOMu lze využít knihoven DOM přímo v PHP + pár postřehů na PHPFashion. Nebo dalších knihoven jako Simple HTML DOM parser. Nic z toho jsem ale osobně netestoval.
JavaScript a jQuery
Pokud je text například výstupem nějakého WYSIWYG editoru, kde uživatel zapomněl URL označit jako odkaz, detekce je složitější. Nyní je lepší využít JavaScriptu, protože procházením DOMu lze jednoduše zjistit, jedná-li se o TextNode. Poté pouze v něm provést nahrazení, pokud žádný jeho rodič není tag <a>. Implementace je pomocí jQuery, nic ale nebrání přepsání do čistého JavaScriptu.
var activeLinks = { // Wrap inactive URL to <a> linkMatchRegex: /((?:https?|ftps?):\/\/|www.)([\w-]+(?:(?:\.[\w-]+)+))(:[0-9]+)?(\/(?:[\w-\.%]+\/?)*\/?)?(\?[\w-%\.]+[^#\s]+)?(#[^\s]+)?/ig, init: function (jQEls) { // Nelezení všech přímých potomků, včetně textových uzlů this.processChildren(jQEls.contents()); }, processChildren: function (wraps) { if (wraps.length < 1) { return; } // nodeType 3 = text node this.processTexts(wraps.filter(function () { return this.nodeType === 3; })); // nodeType 1 = element node this.processChildren(wraps.filter(function () { return this.nodeType === 1; }).contents()); }, processTexts: function (textEls) { if (textEls.length < 1) { return; } var t = this; textEls.each(function () { var jqEl = $(this); // Textový uzel nemá žádného předka <a> if (jqEl.parents("a").length === 0) { var splitMatches = jqEl.text().split(t.linkMatchRegex); // Rozdělení na obyčejné textové uzly a textové uzly obsahující URL t.wrapLink(jqEl, splitMatches); } }); }, wrapLink: function (jqEl, splitMatches) { var tmpEl = $("<div>"); for(var i = 0; i < splitMatches.length; i++){ // Zpětné spojení všech textových uzlů, pokud je URL // vloží se jako "element node" <a> if (splitMatches[i].match(this.linkMatchRegex)) { tmpEl.append( $("<a>").attr("href", splitMatches[i]).text(splitMatches[i]) ); }else{ tmpEl.append(document.createTextNode(splitMatches[i])); } } // Původní textový uzel může být 1 z mnoha ve stejném rodiči, // pro správné nahrazení se prvně obalí a až poté se nahradí var lineWrap = jqEl.wrap("<span>").parent(); lineWrap.replaceWith(tmpEl.html()); } }; $(function () { activeLinks.init($(".js-activeLinks")); });
S osobními zkušenostmi či postřehy se můžete podělit v komentářích
K tomuto článku již není možné přidávat další komentáře